跳到主要内容

IoC 容器的简单实现之配置文件的读取

主要实现思路

  1. 读取配置文件,解析 XML。这里使用 Dom4J 这个包来解析 xml
  2. 通过反射机制实例化配置文件中所配置的所有 bean

导入 XML 解析依赖

参考资料 Dom4J的使用

这里主要记录一下 容器关于创建 Bean 的实现原理

<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>

例子所使用的 Beans

<bean id="Student" class="com.alsritter.bean.Student">
<property name="name" value="张三"></property>
<property name="gender" value=""></property>
<property name="age" value="18"></property>
<property name="identity" ref="Identity"></property>
</bean>

<bean id="Identity" class="com.alsritter.bean.Identity">
<property name="id" value="1"></property>
<property name="stature" value="180.0"></property>
<property name="address" ref="Address"></property>
</bean>

<bean id="Address" class="com.alsritter.bean.Address">
<property name="state" value="中国"></property>
<property name="province" value="广东"></property>
<property name="city" value="深圳"></property>
</bean>

创建 ApplicationContext 接口

public interface ApplicationContext {
public Object getBean(String id);
}

集中维护 Bean

这里使用 HashMap 来维护读取到的 Bean,这个 key 对标 Xml 里的 id

Map<String, Object> ioc = new HashMap<String, Object>();

创建个 ClassPathXmlApplicationContext 实现类去继承这个接口

public Object getBean(String id) {
// TODO 自动生成的方法存根
return ioc.get(id);
}

再创建一个构造函数,在构造函数里把配置文件中的所有 Bean 创建出来

public ClassPathXmlApplicationContext(String path) {
SAXReader reader = new SAXReader();
}

读取配置文件生成 Bean

遍历配置文件读取 Bean

把遍历所有 Bean 节点,包括赋值给 Bean 的字段(先只考虑基本类型)

// 方便直接从资源文件夹读取配置文件
Document document = reader.read("./src/main/resources/" + path);
Element root = document.getRootElement();
// 创建一个迭代器,方便遍历子节点
Iterator<Element> iterable = root.elementIterator();
while (iterable.hasNext()) {
Element element = iterable.next();
// 获取节点属性(id,class)
String id = element.attributeValue("id");
String className = element.attributeValue("class");

// 因为要存到HashMap里面,所以需要一个对象。这个对象通过反射来创建(注:不标明类型返回默认为Object)
Class tempClass = Class.forName(className);
// 获取无参构造
Constructor constructor = tempClass.getConstructor();
// 然后就可以根据这个无参构造实例化一个对象了
Object object = constructor.newInstance();

// 给目标对象赋值,同遍历根节点一样,这里也遍历Bean的子节点
Iterator<Element> iteratorSon = element.elementIterator();
while (iteratorSon.hasNext()) {
Element elementSon = iteratorSon.next();
// 取得Bean字段的名称与要赋的值
String name = elementSon.attributeValue("name");
String value = elementSon.attributeValue("value");

// 这里就用到了Bean规范了,通过字符串拼接生成标准的setter方法名
String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
// 取得字段
Field field = itemClass.getDeclaredField(name);
// 这个函数原型为:getDeclaredMethod(String var1, Class... var2),需要把类型传进去,不然找不到这个方法
Method method = itemClass.getDeclaredMethod(methodName, field.getType());
// 然后这里要做个类型判断,因为只支持基本类型。所以比对一下参数类型,再把这个value转成相应类型的值就行了
Class<?> type = field.getType();
if (type == int.class) {
method.invoke(object, Integer.valueOf(value));
} else if (type == String.class) {
method.invoke(object, value);
} else if (type == Double.class) {
method.invoke(object, Double.valueOf(value));
}
}

对注入进来的 Bean 赋值

private void fillField(Class itemClass, String name, String value, Object object)
throws NoSuchFieldException, SecurityException, NoSuchMethodException, NumberFormatException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// 这里就用到了Bean规范了,通过字符串拼接生成标准的setter方法名
String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
// 取得字段,方便传值。
Field field = itemClass.getDeclaredField(name);
// 这个函数原型为:getDeclaredMethod(String var1, Class... var2),需要把类型传进去,不然找不到这个方法
Method method = itemClass.getDeclaredMethod(methodName, field.getType());
// 然后这里要做个类型判断,因为只支持基本类型。所以比对一下参数类型,再把这个value转成相应类型的值就行了
Class<?> type = field.getType();
if (type == int.class) {
method.invoke(object, Integer.valueOf(value));
} else if (type == String.class) {
method.invoke(object, value);
} else if (type == Double.class) {
method.invoke(object, Double.valueOf(value));
}
}

递归注册引用类型的 Bean

然后就要考虑引用类型的字段了,因为是通过 ref 传入 id 来进行赋值的。所以需要再次遍历一遍所有的 Bean,这次只需要找到相应 id 的 Bean 就行了。但是因为 ref 进来的 Bean 可能里面还有 ref 其他的 bean。所以这时就需要使用递归了

因为 ref 对象都有这个属性,反之没有的则不用递归。所以判断 ref 这个属性是否为空就行了

// 通过判断是否有ref来判断是否为引用类型
String ref = elementSon.attributeValue("ref");
// 把赋值添加一个判断
if (ref == null) {
fillField(tempClass, name, value, object);
} else {
//如果是ref则把objet添加到这个字段上
Object tempObject = fillRefField(ref, root);
// 这里就用到了Bean规范了,通过字符串拼接生成标准的setter方法名
String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
// 取得字段,方便传值。
Field field = tempClass.getDeclaredField(name);
// 这个函数原型为:getDeclaredMethod(String var1, Class... var2),需要把类型传进去,不然找不到这个方法
Method method = tempClass.getDeclaredMethod(methodName, field.getType());
// 参数一:需要被填入的对象 参数二:填充的value
method.invoke(object, tempObject);
}

然后创建一个 fillRefField 方法专门用来进行递归

private Object fillRefField(String ref, Element root) {
Object object = null;
try {
Iterator<Element> iterable = root.elementIterator();
String id = null;
while (iterable.hasNext()) {
Element element = iterable.next();
id = element.attributeValue("id");
// 找到这个bean
if (id.equals(ref)) {
String className = element.attributeValue("class");
Class tempClass = Class.forName(className);
Constructor constructor = tempClass.getConstructor();
object = constructor.newInstance();
// 给目标对象赋值,同遍历根节点一样,这里也遍历Bean的子节点
Iterator<Element> iteratorSon = element.elementIterator();
while (iteratorSon.hasNext()) {
Element elementSon = iteratorSon.next();
String name = elementSon.attributeValue("name");
String value = elementSon.attributeValue("value");
String itemRef = elementSon.attributeValue("ref");
// 再进行一次判断。如果还有ref则再次调用这个方法(递归)
if (itemRef == null) {
fillField(tempClass, name, value, object);
} else {
//如果没有则创建这个类后,把它传给这个字段
Object tempObject = fillRefField(itemRef, root);
String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
Field field = tempClass.getDeclaredField(name);
Method method = tempClass.getDeclaredMethod(methodName, field.getType());
method.invoke(object, tempObject);
}
}

}
}
} catch (Exception e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return object;
}

下面是测试方法是否正确

单元测试

@Test
public void test() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
System.out.println(applicationContext.getBean("Student"));
}
//结果:Student(name=张三, gender=男, age=18, identity=Identity(id=1, stature=180.0, address=Address(state=中国, province=广东, city=深圳)))

全部代码

package com.alsritter.MyIoC;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
* @ClassName: ClassPathXmlApplicationContext
* @Description:TODO(描述这个类的作用)
* @author:alsritter
* @date: 2020年3月8日 下午2:53:59
* @Copyright:
*/
public class ClassPathXmlApplicationContext implements ApplicationContext {

Map<String, Object> ioc = new HashMap<String, Object>();

/**
* @Title: ClassPathXmlApplicationContext @Description:加载配置文件,并把这些Bean存到Map里 @param: @throws
*/
public ClassPathXmlApplicationContext(String path) {
SAXReader reader = new SAXReader();
try {
// 方便直接从资源文件夹读取配置文件
Document document = reader.read("./src/main/resources/" + path);
Element root = document.getRootElement();
// 创建一个迭代器,方便遍历子节点
Iterator<Element> iterable = root.elementIterator();
while (iterable.hasNext()) {
Element element = iterable.next();
// 获取节点属性(id,class)
String id = element.attributeValue("id");
String className = element.attributeValue("class");
// 因为要存到HashMap里面,所以需要一个对象。这个对象通过反射来创建(注:不标明类型返回默认为Object)
Class tempClass = Class.forName(className);
// 获取无参构造
Constructor constructor = tempClass.getConstructor();
// 然后就可以根据这个无参构造实例化一个对象了
Object object = constructor.newInstance();
// 给目标对象赋值,同遍历根节点一样,这里也遍历Bean的子节点
Iterator<Element> iteratorSon = element.elementIterator();
while (iteratorSon.hasNext()) {
Element elementSon = iteratorSon.next();
String name = elementSon.attributeValue("name");
String value = elementSon.attributeValue("value");
// 通过判断是否有ref来判断是否为引用类型
String ref = elementSon.attributeValue("ref");
// 如果是基本类型
if (ref == null) {
fillField(tempClass, name, value, object);
} else {
//如果是ref则把objet添加到这个字段上
Object tempObject = fillRefField(ref, root);
// 这里就用到了Bean规范了,通过字符串拼接生成标准的setter方法名
String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
// 取得字段,方便传值。
Field field = tempClass.getDeclaredField(name);
// 这个函数原型为:getDeclaredMethod(String var1, Class... var2),需要把类型传进去,不然找不到这个方法
Method method = tempClass.getDeclaredMethod(methodName, field.getType());
// 参数一:需要被填入的对象 参数二:填充的value
method.invoke(object, tempObject);
}
// 最后存到HashMap里
ioc.put(id, object);
}
}

} catch (Exception e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}

/**
* @Title: fillField
* @Description: 填充一个字段
* @param: itemClass
* @param: name
* @param: value
* @param: object
*/
private void fillField(Class itemClass, String name, String value, Object object)
throws NoSuchFieldException, SecurityException, NoSuchMethodException, NumberFormatException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// 这里就用到了Bean规范了,通过字符串拼接生成标准的setter方法名
String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
// 取得字段,方便传值。
Field field = itemClass.getDeclaredField(name);
// 这个函数原型为:getDeclaredMethod(String var1, Class... var2),需要把类型传进去,不然找不到这个方法
Method method = itemClass.getDeclaredMethod(methodName, field.getType());
// 然后这里要做个类型判断,因为只支持基本类型。所以比对一下参数类型,再把这个value转成相应类型的值就行了
Class<?> type = field.getType();
if (type == int.class) {
method.invoke(object, Integer.valueOf(value));
} else if (type == String.class) {
method.invoke(object, value);
} else if (type == Double.class) {
method.invoke(object, Double.valueOf(value));
}
}

/**
* @Title: fillRefField @Description:
* 使用递归的方式把这个对象的字段填满(应对ref引用其他类型的情况) @param: @param
* element @param: @return @return: Object @throws
*/
private Object fillRefField(String ref, Element root) {
Object object = null;
try {
Iterator<Element> iterable = root.elementIterator();
String id = null;
while (iterable.hasNext()) {
Element element = iterable.next();
id = element.attributeValue("id");
// 找到这个bean
if (id.equals(ref)) {
//创建一个这个ref对象
String className = element.attributeValue("class");
Class tempClass = Class.forName(className);
// 获取无参构造
Constructor constructor = tempClass.getConstructor();
// 然后就可以根据这个无参构造实例化一个对象了
object = constructor.newInstance();
// 给目标对象赋值,同遍历根节点一样,这里也遍历Bean的子节点
Iterator<Element> iteratorSon = element.elementIterator();
while (iteratorSon.hasNext()) {
Element elementSon = iteratorSon.next();
String name = elementSon.attributeValue("name");
String value = elementSon.attributeValue("value");
// 通过判断是否有ref来判断是否为引用类型(递归)
String itemRef = elementSon.attributeValue("ref");
// 如果是基本类型就调用fillField把对象这个字段赋值
if (itemRef == null) {
fillField(tempClass, name, value, object);
} else {
//如果是ref则把objet添加到这个字段上
Object tempObject = fillRefField(itemRef, root);
// 这里就用到了Bean规范了,通过字符串拼接生成标准的setter方法名
String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
// 取得字段,方便传值。
Field field = tempClass.getDeclaredField(name);
// 这个函数原型为:getDeclaredMethod(String var1, Class... var2),需要把类型传进去,不然找不到这个方法
Method method = tempClass.getDeclaredMethod(methodName, field.getType());
// 参数一:需要被填入的对象 参数二:填充的value
method.invoke(object, tempObject);
}
}

}
}
} catch (Exception e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return object;
}

/**
* <p>
* Title: getBean
* </p>
* <p>
* Description:
* </p>
*
* @param id
* @return
* @see com.alsritter.MyIoC.ApplicationContext#getBean(java.lang.String)
*/
public Object getBean(String id) {
// TODO 自动生成的方法存根
return ioc.get(id);
}

}